package com.chatplus.application.common.util;

import com.chatplus.application.common.exception.BadRequestException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.Calendar;
import java.util.Date;

@SuppressWarnings({"WeakerAccess", "unused"})
public abstract class TimeUtils {
    public static final String TIME_FORMAT_NONE_SECOND = "HH:mm";
    public static final String TIME_FORMAT = "HH:mm:ss";
    public static final String TIME_FORMAT_MM_DD_HH_MM = "MM-dd HH:mm";
    public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DATE_TIME_MILLI_SECOND_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
    public final static ZoneId ZONE_ID = ZoneId.systemDefault();
    public final static ZoneOffset ZONE_OFFSET = ZONE_ID.getRules().getOffset(Instant.now());
    public final static DayOfWeek FirstDayOfWeek = WeekFields.ISO.getFirstDayOfWeek();

    public static final String DATE_TIME_PATTERN = "yyyy/MM/dd HH:mm";

    private static String[] PARSE_PATTERNS = {
        "yyyy-MM", "yyyy-MM-dd", "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss",
        "yyyy/MM", "yyyy/MM/dd", "yyyy/MM/dd HH:mm", "yyyy/MM/dd HH:mm:ss",
        "yyyy.MM", "yyyy.MM.dd", "yyyy.MM.dd HH:mm", "yyyy.MM.dd HH:mm:ss"};

    public static Date parseDate(String str) {
        if (str == null || str.isEmpty()) {
            return null;
        }
        try {
            return DateUtils.parseDate(str, PARSE_PATTERNS);
        } catch (ParseException e) {
            return null;
        }
    }

    public static Instant toInstant(LocalDateTime localDateTime) {
        return localDateTime.toInstant(ZONE_OFFSET);
    }

    public static Date localDateTimeToDate(LocalDateTime localDateTime) {
        return Date.from(toInstant(localDateTime));
    }

    public static String getNowFormat() {
        return getDateTimeFormat(new Date());
    }

    public static String getDateTimeFormat(Instant instant) {
        return getDateTimeFormat(new Date(instant.toEpochMilli()));
    }

    public static String getDateTimeFormat(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_TIME_FORMAT);
        return simpleDateFormat.format(date);
    }

    /**
     * 目标时间是否在指定范围（包含起止时间在内）
     * 如果传入的截止时间小于起始时间，则表示截止时间为次日的时间
     *
     * @param target       要判断的目标时间
     * @param startTimeStr 起始时间 HH:mm:ss
     * @param endTimeStr   截至时间 HH:mm:ss
     * @return 目标时间是否在指定范围
     */
    public static boolean isTargetTimeBetweenDuration(Date target, SimpleDateFormat simpleDateFormat, String startTimeStr, String endTimeStr) {
        String targetStr = simpleDateFormat.format(target);
        try {
            Date targetTime = simpleDateFormat.parse(targetStr);
            Date startTime = simpleDateFormat.parse(startTimeStr);
            Date endTime = simpleDateFormat.parse(endTimeStr);

            if (endTime.before(startTime)) {
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(endTime);
                calendar.add(Calendar.DAY_OF_MONTH, 1);
                endTime = calendar.getTime();
            }

            if (!targetTime.before(startTime) && !targetTime.after(endTime)) {
                return true;
            }
        } catch (ParseException ignored) {
        }

        return false;
    }

    public static boolean isTargetTimeBetweenDuration(Date target, String startTimeStr, String endTimeStr) {
        Assert.notEmpty(startTimeStr);

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
            StringUtils.countMatches(startTimeStr, ":") > 1
                ? TIME_FORMAT
                : TIME_FORMAT_NONE_SECOND);
        return isTargetTimeBetweenDuration(target, simpleDateFormat, startTimeStr, endTimeStr);
    }

    public static boolean isTargetTimeBetweenDuration(String startTimeStr, String endTimeStr) {
        return isTargetTimeBetweenDuration(new Date(), startTimeStr, endTimeStr);
    }

    /**
     * 获取距离当前时间最近的下一个固定的时:分:秒
     *
     * @param timeStr HH:mm / HH:mm:ss格式时间字符串
     */
    public static Instant getTimeByStr(Date target, String timeStr) {

        Assert.notEmpty(timeStr);

        String pattern = StringUtils.countMatches(timeStr, ":") > 1
            ? "yyyy-MM-dd " + timeStr
            : "yyyy-MM-dd " + timeStr + ":00";

        try {
            String format = new SimpleDateFormat(pattern).format(target);
            Date parse = new SimpleDateFormat(DATE_TIME_FORMAT).parse(format);

            if (parse.before(new Date())) {
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(parse);
                calendar.add(Calendar.DAY_OF_MONTH, 1);
                parse = calendar.getTime();
            }

            return parse.toInstant();
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    public static Instant getTimeByStr(String timeStr) {
        return getTimeByStr(new Date(), timeStr);
    }

    public static Instant getStartOfThisHour() {
        LocalTime time = LocalTime.now();

        return LocalDateTime
            .of(LocalDate.now(), LocalTime.of(time.getHour(), 0))
            .toInstant(ZONE_OFFSET);
    }

    public static Instant getStartOfThisDay(String timeStr, String pattern) {
        LocalDateTime localDateTime = TimeUtils.convertStringToLocalDateTime(timeStr, pattern);
        return localDateTime.toLocalDate().atStartOfDay().toInstant(TimeUtils.ZONE_OFFSET);
    }

    public static Instant getStartOfThisDay() {
        return LocalDate.now().atStartOfDay().toInstant(ZONE_OFFSET);
    }

    public static Instant getEndOfThisDay(String timeStr, String pattern) {
        LocalDateTime localDateTime = TimeUtils.convertStringToLocalDateTime(timeStr, pattern);
        return LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.MAX).toInstant(TimeUtils.ZONE_OFFSET);
    }

    public static Instant getEndOfThisDay() {
        return LocalDateTime
            .of(LocalDate.now(), LocalTime.MAX)
            .toInstant(ZONE_OFFSET);
    }

    public static Instant getStartOfThisWeek() {
        return LocalDateTime
            .of(LocalDate.now().with(FirstDayOfWeek), LocalTime.MIDNIGHT)
            .toInstant(ZONE_OFFSET);
    }

    public static Instant getStartOfThisMonth() {
        return LocalDateTime
            .of(LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()), LocalTime.MIDNIGHT)
            .toInstant(ZONE_OFFSET);
    }

    public static Instant getStartOfThisYear() {
        return LocalDateTime
            .of(LocalDate.now().with(TemporalAdjusters.firstDayOfYear()), LocalTime.MIDNIGHT)
            .toInstant(ZONE_OFFSET);
    }

    public static Instant getDurationStart(ChronoUnit durationUnit) {
        switch (durationUnit) {
            case HOURS:
                return TimeUtils.getStartOfThisHour();
            case WEEKS:
                return TimeUtils.getStartOfThisWeek();
            case MONTHS:
                return TimeUtils.getStartOfThisMonth();
            case YEARS:
                return TimeUtils.getStartOfThisYear();
            case DAYS:
            default:
                return TimeUtils.getStartOfThisDay();
        }
    }

    public static DayOfWeek getThisDayOfWeek() {
        return LocalDate.now().getDayOfWeek();
    }

    public static LocalDateTime convertStringToLocalDateTime(String timeString, String pattern) throws BadRequestException {
        try {
            return LocalDateTime.parse(timeString, DateTimeFormatter.ofPattern(pattern));
        } catch (Exception e) {
            throw new BadRequestException("时间[" + timeString + "]格式不正确，应该为[" + pattern + "]且时间范围有效", null);
        }
    }

    public static Instant convertStringToInstant(String timeString, String pattern) throws BadRequestException {
        try {
            return toInstant(LocalDateTime.parse(timeString, DateTimeFormatter.ofPattern(pattern)));
        } catch (Exception e) {
            throw new BadRequestException("时间[" + timeString + "]格式不正确，应该为[" + pattern + "]且时间范围有效", null);
        }
    }

    /**
     * 把日期字符串转为UTC的毫秒数
     */
    public static long convertStringToUTCEpochMilli(String timeString, String pattern) {
        return localDateTimeToUTCEpochMilli(convertStringToLocalDateTime(timeString, pattern));
    }

    public static long localDateTimeToUTCEpochMilli(LocalDateTime time) {
        return time.toInstant(ZoneOffset.UTC).toEpochMilli();
    }

    public static LocalDateTime ofInstant(Instant instant) {
        if (instant == null) {
            return null;
        }
        return LocalDateTime.ofInstant(instant, ZONE_ID);
    }

    public static Date instantToDate(Instant instant) {
        if (instant == null) {
            return null;
        }
        return new Date(instant.toEpochMilli());
    }

    public static LocalDateTime dateToLocalDateTime(Date date) {
        if (date == null) {
            return null;
        }
        return date.toInstant().atZone(ZONE_ID).toLocalDateTime();
    }

    public static LocalDateTime dateToLocalDateTimeWithNanoZero(Date date) {
        LocalDateTime localDateTime = dateToLocalDateTime(date);
        if (localDateTime == null) {
            return null;
        }
        return localDateTime.withNano(0);
    }

    public static Instant plusSpecifiedTime(long amountToAdd, ChronoUnit unit, int hourOfDay, int minute, int second, int millisecond) {
        if (unit == null) {
            throw new BadRequestException("时间单位不能为空");
        }
        Instant instant = getCurrentDaySpecifiedTime(hourOfDay, minute, second, millisecond);
        return instant.plus(amountToAdd, unit);
    }

    public static Instant minusSpecifiedTime(long amountToSubtract, ChronoUnit unit, int hourOfDay, int minute, int second, int millisecond) {
        if (unit == null) {
            throw new BadRequestException("时间单位不能为空");
        }
        Instant instant = getCurrentDaySpecifiedTime(hourOfDay, minute, second, millisecond);
        return instant.minus(amountToSubtract, unit);
    }

    public static Instant getCurrentDaySpecifiedTime(int hourOfDay) {
        return getCurrentDaySpecifiedTime(hourOfDay, 0, 0, 0);
    }

    public static Instant getCurrentDaySpecifiedTime(int hourOfDay, int minute) {
        return getCurrentDaySpecifiedTime(hourOfDay, minute, 0, 0);
    }

    public static Instant getCurrentDaySpecifiedTime(int hourOfDay, int minute, int second, int millisecond) {
        Calendar instance = Calendar.getInstance();
        instance.set(Calendar.HOUR_OF_DAY, hourOfDay);
        instance.set(Calendar.MINUTE, minute);
        instance.set(Calendar.SECOND, second);
        instance.set(Calendar.MILLISECOND, millisecond);
        return instance.getTime().toInstant();
    }

    public static String convertTimeTozhCN(Instant instant) {
        return convertTimeTozhCN(Date.from(instant));
    }

    public static String convertTimeTozhCN(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1;
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        return year + "年" + month + "月" + day + "日" + (minute > 0 ? hour + "点" + minute + "分" : hour + "点");
    }

    public static String format(Instant instant) {
        return format(instant, DATE_TIME_FORMAT);
    }

    public static String format(Instant instant, String pattern) {
        Date from = Date.from(instant);
        return new SimpleDateFormat(pattern).format(from);
    }

    public static String format(String pattern, LocalDateTime localDateTime) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
        return dateTimeFormatter.format(localDateTime);
    }

    public static String formatTotalWithMilliSecond(LocalDateTime localDateTime) {
        return format(DATE_TIME_MILLI_SECOND_FORMAT, localDateTime);
    }

    public static int getMonthsBetween(LocalDateTime before, LocalDateTime after) {
        LocalDate localDateBefore = before.toLocalDate().withDayOfMonth(1);
        LocalDate localDateAfter = after.toLocalDate().withDayOfMonth(1);
        int months = Period.between(localDateBefore, localDateAfter).getMonths();
        int year = Period.between(localDateBefore, localDateAfter).getYears();
        return year * 12 + months;
    }

    public static long getDaysBetween(LocalDateTime before, LocalDateTime after) {
        return after.until(before, ChronoUnit.DAYS);
    }

    public static long getSecondsBetween(LocalDateTime before, LocalDateTime after) {
        return after.until(before, ChronoUnit.SECONDS);
    }

    /**
     * Long转Instant
     */
    public static Instant secondsLongToInstant(Long epochSecond, boolean returnDefaultNowTime) {
        if (epochSecond != null && epochSecond > 0) {
            return Instant.ofEpochSecond(epochSecond);
        }
        return returnDefaultNowTime ? Instant.now() : null;
    }

    public static String instantToDateString(Instant instant) {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return localDateTime.format(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN));
    }
}
